package com.rem.redis.service;

import cn.hutool.core.util.BooleanUtil;
import com.rem.redis.config.JedisPoolUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;

import java.util.List;


/**
 * @author Rem
 * @date 2022-05-04
 */

@Service
@Log4j2
public class SeckillService {

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    //库存key
    private static final String STOCK_KEY = "sk:";
    //秒杀成功user key
    private static final String SK_SUSS = "successUser:";

    /**
     * 秒杀
     * 并发下会产生超卖
     *
     * @param userId    用户id
     * @param productId 商品id
     * @return
     */
    public boolean seckill(String userId, Integer productId) {
        //1 uid和prodid非空判断
        if (userId == null || productId == null) {
            log.error("参数错误");
            return false;
        }

        //2 获取库存，如果库存null，秒杀还没有开始
        Integer stock = (Integer) redisTemplate.opsForValue().get(STOCK_KEY + productId);
        if (stock == null) {
            log.error("秒杀还没有开始");
            return false;
        }

        //3 判断用户是否重复秒杀操作
        if (BooleanUtil.isTrue(redisTemplate.opsForSet().isMember(SK_SUSS + productId, userId))) {
            log.error("已经秒杀过");
            return false;
        }

        //4 判断如果商品数量，库存数量小于1，秒杀结束
        if (stock < 1) {
            log.error("秒杀已经结束了");
            return false;
        }

        //5 秒杀过程
        redisTemplate.opsForValue().decrement(STOCK_KEY + productId);
        redisTemplate.opsForSet().add(SK_SUSS + productId, userId);
        log.warn("秒杀成功:" + userId);
        return true;

    }


    /**
     * 秒杀
     * multi 乐观锁操作
     * 使用redisTemplate.multi 会启用一个新的连接 因为他会先执行discard 所以导致开始事务和批量(multi)不一致
     * 只能引入jedis操作
     *
     * @param uid
     * @param productId
     * @return
     */
    public boolean doSecKill(String uid, String productId) {
        //1 uid和prodid非空判断
        if (uid == null || productId == null) {
            return false;
        }

        //2 连接redis
        //通过连接池得到jedis对象
        JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedisPoolInstance.getResource();


        //3 监视库存
        jedis.watch(STOCK_KEY + productId);

        //4 获取库存，如果库存null，秒杀还没有开始
        String kc = jedis.get(STOCK_KEY + productId);
        if (kc == null) {
            System.out.println("秒杀还没有开始，请等待");
            jedis.close();
            return false;
        }

        // 5 判断用户是否重复秒杀操作
        if (jedis.sismember(SK_SUSS + productId, uid)) {
            System.out.println("已经秒杀成功了，不能重复秒杀");
            jedis.close();
            return false;
        }

        //6 判断如果商品数量，库存数量小于1，秒杀结束
        if (Integer.parseInt(kc) <= 0) {
            System.out.println("秒杀已经结束了");
            jedis.close();
            return false;
        }

        //7 秒杀过程
        //使用事务
        Transaction multi = jedis.multi();

        //组队操作
        multi.decr(STOCK_KEY + productId);
        multi.sadd(SK_SUSS + productId, uid);

        //执行
        List<Object> results = multi.exec();

        if (results == null || results.size() == 0) {
            System.out.println("秒杀失败了....");
            jedis.close();
            return false;
        }

        System.out.println("秒杀成功了..");
        jedis.close();
        return true;
    }

    public boolean doSecKillWithScript(String userId, String productId) {
        JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedispool.getResource();

        String sha1 = jedis.scriptLoad(secKillScript);
        Object result = jedis.evalsha(sha1, 2, userId, productId);

        String reString = String.valueOf(result);
        jedis.close();
        if ("0".equals(reString)) {
            System.err.println("已抢空！！");
        } else if ("1".equals(reString)) {
            System.out.println("抢购成功！！！！");
            return true;
        } else if ("2".equals(reString)) {
            System.err.println("该用户已抢过！！");
        } else {
            System.err.println("抢购异常！！");

        }
        return false;
    }


    static String secKillScript = "local userid=KEYS[1];\r\n" +
            "local prodid=KEYS[2];\r\n" +
            "local qtkey='sk:'..prodid..\":qt\";\r\n" +
            "local usersKey='sk:'..prodid..\":usr\";\r\n" +
            "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
            "if tonumber(userExists)==1 then \r\n" +
            "   return 2;\r\n" +
            "end\r\n" +
            "local num= redis.call(\"get\" ,qtkey);\r\n" +
            "if tonumber(num)<=0 then \r\n" +
            "   return 0;\r\n" +
            "else \r\n" +
            "   redis.call(\"decr\",qtkey);\r\n" +
            "   redis.call(\"sadd\",usersKey,userid);\r\n" +
            "end\r\n" +
            "return 1";
}
